package org.mozhu.mboot.core.data.support;

import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.commons.collections.CollectionUtils;

import org.mozhu.mboot.core.data.BaseService;
import org.mozhu.mboot.core.data.DynamicSpecifications;
import org.mozhu.mboot.core.data.RepositoryHelper;
import org.mozhu.mboot.core.data.entity.IEntity;
import org.mozhu.mboot.core.data.entity.Treeable;
import org.mozhu.mboot.core.data.filter.Operator;
import org.mozhu.mboot.core.data.filter.SearchFilter;
import org.mozhu.mboot.core.data.filter.SimpleSearchFilter;
import org.mozhu.mboot.core.util.ReflectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.persistence.EntityManager;
import java.io.Serializable;
import java.util.*;

@SuppressWarnings({ "rawtypes", "unchecked" })
public abstract class BaseTreeableService<M extends IEntity<ID> & Treeable<ID>, ID extends Serializable> extends BaseService<M, ID> implements IBaseTreeableService<M, ID> {
	protected String DELETE_CHILDREN_QL;
	protected String UPDATE_CHILDREN_PARENT_IDS_QL;
	protected String FIND_SELF_AND_NEXT_SIBLINGS_QL;
	protected String FIND_NEXT_WEIGHT_QL;

	protected EntityManager entityManager;
	protected RepositoryHelper repositoryHelper;

	protected Class<M> entityClass;

	@Autowired
	public void setEntityManager(EntityManager entityManager) {
		this.entityManager = entityManager;
		this.entityClass = ReflectionUtils.findParameterizedType(getClass(), 0);
		this.repositoryHelper = new RepositoryHelper(entityManager, this.entityClass);
		String entityName = this.repositoryHelper.getEntityName();
		DELETE_CHILDREN_QL = String.format("delete from %s where id=?1 or parentIds like concat(?2, %s)", entityName, "'%'");
		UPDATE_CHILDREN_PARENT_IDS_QL = String.format("update %s set parentIds=(?1 || substring(parentIds, length(?2)+1)) where parentIds like concat(?2, %s)", entityName, "'%'");
		FIND_SELF_AND_NEXT_SIBLINGS_QL = String.format("from %s where parentIds = ?1 and weight>=?2 order by weight asc", entityName);
		FIND_NEXT_WEIGHT_QL = String.format("select case when max(weight) is null then 1 else (max(weight) + 1) end from %s where parentId = ?1", entityName);
	}

	@Override
	public List<M> findAll() {
		return baseRepository.findAll(new Sort(Direction.ASC, "weight"));
	}

	@Override
	public M save(M m) {
		// TODO看是否真的需要这段代码
		if (!m.isRoot()) {
			M parent = baseRepository.findById(m.getParentId()).get();
			m.setParentId(m.getParentId());
			m.setParentIds(parent.makeSelfAsNewParentIds());
		}
		if (m.getWeight() == null) {
			m.setWeight(nextWeight(m.getParentId()));
		}
		return super.save(m);
	}

	@Override
	@Transactional
	public void deleteSelfAndChild(M m) {
		repositoryHelper.batchUpdate(DELETE_CHILDREN_QL, m.getId(), m.makeSelfAsNewParentIds());
	}

	@Override
	public void deleteSelfAndChild(List<M> mList) {
		for (M m : mList) {
			deleteSelfAndChild(m);
		}
	}

	@Override
	public void appendChild(M parent, M child) {
		child.setParentId(parent.getId());
		child.setParentIds(parent.makeSelfAsNewParentIds());
		child.setWeight(nextWeight(parent.getId()));
		save(child);
	}

	@Override
	public int nextWeight(ID id) {
		return repositoryHelper.findOne(FIND_NEXT_WEIGHT_QL, id);
	}

	/**
	 * 移动节点 根节点不能移动
	 * 
	 * @param source
	 *            源节点
	 * @param target
	 *            目标节点
	 * @param moveType
	 *            位置
	 */
	@Transactional
	@Override
	public void move(M source, M target, String moveType) {
		if (source == null || target == null) { // 根节点也可以移动
			return;
		}

		// 如果是相邻的兄弟 直接交换weight即可
		boolean isSibling = source.getParentId().equals(target.getParentId());
		boolean isNextOrPrevMoveType = "next".equals(moveType) || "prev".equals(moveType);
		if (isSibling && isNextOrPrevMoveType && Math.abs(source.getWeight() - target.getWeight()) == 1) {

			// 无需移动
			if ("next".equals(moveType) && source.getWeight() > target.getWeight()) {
				return;
			}
			if ("prev".equals(moveType) && source.getWeight() < target.getWeight()) {
				return;
			}

			int sourceWeight = source.getWeight();
			source.setWeight(target.getWeight());
			target.setWeight(sourceWeight);
			baseRepository.saveAll(Lists.newArrayList(source, target));
			return;
		}

		// 移动到目标节点之后
		if ("next".equals(moveType)) {
			List<M> siblings = findSelfAndNextSiblings(target.getParentIds(), target.getWeight());
			siblings.remove(0);// 把自己移除

			if (siblings.size() == 0) { // 如果没有兄弟了 则直接把源的设置为目标即可
				int nextWeight = nextWeight(target.getParentId());
				updateSelftAndChild(source, target.getParentId(), target.getParentIds(), nextWeight);
				return;
			} else {
				moveType = "prev";
				target = siblings.get(0); // 否则，相当于插入到实际目标节点下一个节点之前
			}
		}

		// 移动到目标节点之前
		if ("prev".equals(moveType)) {

			List<M> siblings = findSelfAndNextSiblings(target.getParentIds(), target.getWeight());
			// 兄弟节点中包含源节点
			if (siblings.contains(source)) {
				// 1 2 [3 source] 4
				siblings = siblings.subList(0, siblings.indexOf(source) + 1);
				int firstWeight = siblings.get(0).getWeight();
				for (int i = 0; i < siblings.size() - 1; i++) {
					siblings.get(i).setWeight(siblings.get(i + 1).getWeight());
				}
				siblings.get(siblings.size() - 1).setWeight(firstWeight);
			} else {
				// 1 2 3 4 [5 new]
				int nextWeight = nextWeight(target.getParentId());
				int firstWeight = siblings.get(0).getWeight();
				for (int i = 0; i < siblings.size() - 1; i++) {
					siblings.get(i).setWeight(siblings.get(i + 1).getWeight());
				}
				siblings.get(siblings.size() - 1).setWeight(nextWeight);
				source.setWeight(firstWeight);
				baseRepository.save(source);
				updateSelftAndChild(source, target.getParentId(), target.getParentIds(), source.getWeight());
			}
			return;
		}
		// 否则作为最后孩子节点
		int nextWeight = nextWeight(target.getId());
		updateSelftAndChild(source, target.getId(), target.makeSelfAsNewParentIds(), nextWeight);
	}

	/**
	 * 把源节点全部变更为目标节点
	 * 
	 * @param source
	 * @param newParentIds
	 */
	private void updateSelftAndChild(M source, ID newParentId, String newParentIds, int newWeight) {
		String oldSourceChildrenParentIds = source.makeSelfAsNewParentIds();
		source.setParentId(newParentId);
		source.setParentIds(newParentIds);
		source.setWeight(newWeight);
		save(source);
		String newSourceChildrenParentIds = source.makeSelfAsNewParentIds();
		repositoryHelper.batchUpdate(UPDATE_CHILDREN_PARENT_IDS_QL, newSourceChildrenParentIds, oldSourceChildrenParentIds);
	}

	/**
	 * 查找目标节点及之后的兄弟 注意：值与越小 越排在前边
	 * 
	 * @param parentIds
	 * @param currentWeight
	 * @return
	 */
	protected List<M> findSelfAndNextSiblings(String parentIds, int currentWeight) {
		return repositoryHelper.findAll(FIND_SELF_AND_NEXT_SIBLINGS_QL, parentIds, currentWeight);
	}

	/**
	 * 查看与name模糊匹配的名称
	 * 
	 * @param name
	 * @return
	 */
	@Override
	public Set<String> findNames(List<SearchFilter> searchFilters, Pageable pageable, String name, ID excludeId) {
		if (excludeId != null) {
			searchFilters.addAll(buildExcludeFilter(baseRepository.findById(excludeId).get()));
		}
		searchFilters.add(new SimpleSearchFilter("name", Operator.LIKE, name));

		List<M> list = findBySearchFilters(searchFilters);
		return Sets.newHashSet(Lists.transform(sort(list), new Function<M, String>() {
			@Override
			public String apply(M input) {
				return input.getName();
			}
		}));

	}

	/**
	 * 查询子子孙孙
	 * 
	 * @return
	 */
	@Override
	public List<M> findChildren(List<M> parents, List<SearchFilter> searchFilter) {
		Set<M> children = Sets.newHashSet();
		if (parents != null) {
			for (M m : parents) {
				List<SearchFilter> filters = Lists.newArrayList(searchFilter);
				filters.add(new SimpleSearchFilter("parentIds", Operator.PLIKE, m.makeSelfAsNewParentIds()));
				Specification<M> spec = DynamicSpecifications.bySearchFilter(filters, entityClass);
				children.addAll(baseRepository.findAll(spec));
			}
		}
		return sort(Lists.newArrayList(children));
	}

	@Override
	public List<M> findChildrenByParentIds(List<ID> parentIds, List<SearchFilter> searchFilter) {
		return findChildren(baseRepository.findAllById(parentIds), searchFilter);
	}

	@Override
	public List<M> findChildrenByParentIds(List<ID> parentIds) {
		return findChildren(baseRepository.findAllById(parentIds), Lists.<SearchFilter> newArrayList());
	}

	private Comparator<M> weightAscComparator = new Comparator<M>() {

		@Override
		public int compare(M o1, M o2) {
			return o1.getWeight() - o2.getWeight();
		}

	};

	@Override
	public List<M> sort(List<M> ms) {
		if (CollectionUtils.isEmpty(ms)) {
			return Lists.newArrayList();
		}
		Collections.sort(ms, weightAscComparator);
		return ms;
	}

	@Override
	public List<M> findAllByName(List<SearchFilter> searchFilters, M excludeM) {
		searchFilters.addAll(buildExcludeFilter(excludeM));
		return sort(findBySearchFilters(searchFilters));
	}

	@Override
	public List<M> findAllWithSort(List<SearchFilter> searchFilters) {
		return sort(findBySearchFilters(searchFilters));
	}

	public List<M> findBySearchFilters(List<SearchFilter> searchFilters) {
		Specification<M> spec = DynamicSpecifications.bySearchFilter(searchFilters, entityClass);
		return baseRepository.findAll(spec);
	}

	public List<M> findBySearchFilters(List<SearchFilter> searchFilters, Pageable pageable) {
		return baseRepository.findAll(searchFilters, pageable).getContent();
	}

	@Override
	public List<SearchFilter> buildExcludeFilter(M excludeM) {
		List<SearchFilter> searchFilters = Lists.newArrayList();
		if (excludeM != null) {
			searchFilters.add(new SimpleSearchFilter("id", Operator.NE, excludeM.getId()));
			searchFilters.add(new SimpleSearchFilter("parentIds", Operator.NPLIKE, excludeM.makeSelfAsNewParentIds()));
		}
		return searchFilters;
	}

	/**
	 * 查找根和一级节点
	 * 
	 * @param searchFilter
	 * @return
	 */
	@Override
	public List<M> findRootAndChild(List<SearchFilter> searchFilter) {
		List<SearchFilter> filters = Lists.newArrayList(searchFilter);
		filters.add(new SimpleSearchFilter("parentId", Operator.EQ, 0L));
		List<M> models = findBySearchFilters(filters);
		if (models.size() == 0) {
			return models;
		}
		List<ID> ids = Lists.newArrayList();
		for (int i = 0; i < models.size(); i++) {
			ids.add(models.get(i).getId());
		}
		filters = Lists.newArrayList(searchFilter);
		filters.add(new SimpleSearchFilter("parentId", Operator.IN, ids.toArray()));

		models.addAll(findBySearchFilters(filters));
		return sort(models);
	}

	@Override
	public List<M> findRootAndChild() {
		return findRootAndChild(Lists.<SearchFilter> newArrayList());
	}

	@Override
	public Set<ID> findAncestorIds(Iterable<ID> currentIds) {
		Set<ID> parents = Sets.newHashSet();
		for (ID currentId : currentIds) {
			parents.addAll(findAncestorIds(currentId));
		}
		return parents;
	}

	@Override
	public Set<ID> findAncestorIds(ID currentId) {
		Set ids = Sets.newHashSet();
		M m = findById(currentId);
		if (m == null) {
			return ids;
		}
		for (String idStr : StringUtils.tokenizeToStringArray(m.getParentIds(), "/")) {
			if (!StringUtils.isEmpty(idStr)) {
				ids.add(Long.valueOf(idStr));
			}
		}
		return ids;
	}

	/**
	 * 递归查询祖先，返回时需要排序，辈分高的排在后面
	 * 
	 * @param parentIds
	 * @return
	 */
	@Override
	public List<M> findAncestor(String parentIds) {
		if (StringUtils.isEmpty(parentIds) || "0/".equals(parentIds)) {
			return Collections.EMPTY_LIST;
		}
		String[] ids = StringUtils.tokenizeToStringArray(parentIds, "/");
		List<Long> longIds = Lists.newArrayList();
		for (int i = 0; i < ids.length; i++) {
			if ("0".equals(ids[i])) {
				continue;
			}
			longIds.add(Long.valueOf(ids[i]));
		}
		List<M> result = findBySearchFilters(Lists.newArrayList(new SimpleSearchFilter("id", Operator.IN, longIds.toArray())));
		if (result.size() <= 1) {
			return result;
		}
		// TODO 优化排序算法
		Map<Long, M> resultMap = Maps.newHashMap();
		for (M m : result) {
			resultMap.put((Long) m.getId(), m);
		}

		List<M> orderBy = Lists.newArrayList();
		for (Long longId : longIds) {
			M m = resultMap.get(longId);
			if (m != null) {
				orderBy.add(m);
			}
		}
		return Lists.reverse(orderBy);
	}

	@Override
	public List<ZTree<ID>> convertToZtreeList(List<M> models, boolean open, boolean onlySelectLeaf) {
		List<ZTree<ID>> zTrees = Lists.newArrayList();

		if (models == null || models.isEmpty()) {
			return zTrees;
		}

		for (M m : models) {
			ZTree zTree = convertToZtree(m, open, onlySelectLeaf);
			zTrees.add(zTree);
		}
		return zTrees;
	}

	@Override
	public ZTree<ID> convertToZtree(M m, boolean open, boolean onlyCheckLeaf) {
		ZTree<ID> zTree = new ZTree<ID>();
		zTree.setId(m.getId());
		zTree.setpId(m.getParentId());
		zTree.setName(m.getName());
		zTree.setIconSkin(getIcon(m));
		zTree.setOpen(open);
		zTree.setRoot(m.isRoot());
		zTree.setIsParent(m.isHasChildren());

		if (onlyCheckLeaf && zTree.isIsParent()) {
			zTree.setNocheck(true);
		} else {
			zTree.setNocheck(false);
		}
		return zTree;
	}

	protected String getIcon(M m) {
		String icon = m.getIcon();
		if (StringUtils.isEmpty(icon)) {
			if (m.isRoot()) {
				icon = m.getRootDefaultIcon();
			} else if (m.isLeaf()) {
				icon = m.getLeafDefaultIcon();
			} else {
				icon = m.getBranchDefaultIcon();
			}
		}
		return icon;
	}

}
